Skip to content

fix: useEditorDOMElement hook#2619

Open
matthewlipski wants to merge 7 commits intomainfrom
rerender-on-mount
Open

fix: useEditorDOMElement hook#2619
matthewlipski wants to merge 7 commits intomainfrom
rerender-on-mount

Conversation

@matthewlipski
Copy link
Copy Markdown
Collaborator

@matthewlipski matthewlipski commented Apr 2, 2026

Summary

This PR adds a useEditorDOMElement hook which fetches the editor DOM element. It's necessary for certain cases where components rely on editor.domElement to be defined to attach event listeners. However, when it becomes defined, there is nothing which explicitly triggers a re-render, so the hook is needed to force that re-render.

Internally, useEditorDOMElement uses useEditorState, which has been given a "mount" event to specifically listen for when the view becomes defined/undefined.

Closes #2546

Rationale

See above.

Changes

  • Added "mount" event to useEditorState.
  • Made "all" event in useEditorState also listen for TipTap "create"/"mount"/"unmount" events.
  • Added useEditorDOMElement hook.
  • Replaced direct references to editor.domElement with the hook where necessary.

Impact

N/A

Testing

I wasn't able to reproduce the issue in #2546 locally, and have not seen other cases of this elsewhere. Therefore, no tests have been added.

Screenshots/Video

N/A

Checklist

  • Code follows the project's coding standards.
  • Unit tests covering the new feature have been added.
  • All existing tests pass.
  • The documentation has been updated to reflect the new feature

Additional Notes

Summary by CodeRabbit

  • New Features
    • Added useEditorDOMElement() hook to expose the editor DOM element to React components.
    • Updated popovers, toolbars, suggestion menus, and keyboard handlers to use the new hook for event wiring and positioning.
    • Editor headless status now follows the editor mount/unmount lifecycle.
    • onMount() and onUnmount() now return unsubscribe handles for easier listener cleanup.

@vercel
Copy link
Copy Markdown

vercel bot commented Apr 2, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
blocknote Ready Ready Preview Apr 21, 2026 0:23am
blocknote-website Ready Ready Preview Apr 21, 2026 0:23am

Request Review

@matthewlipski matthewlipski requested a review from nperez0111 April 2, 2026 12:23
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 2, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Replaces computed editor DOM access with a new reactive hook and updates multiple React components/hooks to use it; changes BlockNoteEditor headless to a mutable field driven by TipTap "mount"/"unmount" events and makes onMount/onUnmount return the internal event registrations.

Changes

Cohort / File(s) Summary
Core editor lifecycle
packages/core/src/editor/BlockNoteEditor.ts
Replaced computed headless getter with public headless = true and toggle it on TipTap "mount"/unmount; onMount / onUnmount now return the event-manager subscriptions.
New hook + re-export
packages/react/src/hooks/useEditorDomElement.ts, packages/react/src/index.ts
Added useEditorDOMElement(editor?) which selects ctx.editor?.domElement via useEditorState (new "mount" mode) and re-exported it from the package index.
Toolbar & link interactions
packages/react/src/components/FormattingToolbar/DefaultButtons/CreateLinkButton.tsx, packages/react/src/components/LinkToolbar/LinkToolbarController.tsx
Switched event listener wiring to use editorDOMElement from the new hook and updated effect dependency arrays.
Popovers / FloatingUI references
packages/react/src/components/Popovers/PositionPopover.tsx, packages/react/src/components/SuggestionMenu/.../GridSuggestionMenuController.tsx, packages/react/src/components/SuggestionMenu/SuggestionMenuController.tsx
FloatingUI reference/contextElement now use editorDOMElement (e.g., .firstChild); memoization deps updated to include editorDOMElement.
Suggestion menu keyboard navigation
packages/react/src/components/SuggestionMenu/.../useGridSuggestionMenuKeyboardNavigation.ts, packages/react/src/components/SuggestionMenu/hooks/useSuggestionMenuKeyboardNavigation.ts
Keyboard keydown listeners attach to editorDOMElement; one exported hook parameter renamed to _editor to indicate it’s unused.
Editor state subscription
packages/react/src/hooks/useEditorState.ts
Extended UseEditorStateOptions.on to include "mount", mapped subscription modes to arrays of TipTap events, and updated listener registration to iterate those event lists.

Sequence Diagram

sequenceDiagram
    participant Component as React Component
    participant Hook as useEditorDOMElement()
    participant State as useEditorState()
    participant Editor as BlockNoteEditor
    participant TipTap as TipTap

    Component->>Hook: call useEditorDOMElement(editor?)
    Hook->>State: subscribe select ctx.editor?.domElement (on: "mount")
    State-->>Hook: notify editor.domElement (when available/changes)
    Hook-->>Component: return editorDOMElement
    Component->>Component: attach/remove event listeners / build FloatingUI refs using editorDOMElement
    TipTap-->>Editor: "mount"/"unmount"
    Editor-->>State: emit changes (domElement/headless)
    State-->>Hook: re-emit updated element
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • nperez0111

Poem

🐰 I hopped through DOM and event streams,
I caught the mount and chased unmount dreams,
I handed the editor its shining node,
Now popovers follow the path I showed,
Menus bloom where once they seemed remote. 🌷

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'fix: useEditorDOMElement hook' accurately describes the main change—adding and fixing the useEditorDOMElement hook implementation to address the link toolbar display issue.
Linked Issues check ✅ Passed The PR comprehensively addresses issue #2546 by introducing useEditorDOMElement hook, adding 'mount' event to useEditorState, and replacing direct editor.domElement references throughout components to ensure reliable re-renders when the DOM element becomes available.
Out of Scope Changes check ✅ Passed All changes are directly aligned with addressing #2546: the new hook, useEditorState enhancements for mount events, and component updates all serve the objective of detecting and responding to editor DOM element availability.
Description check ✅ Passed The PR description is mostly complete, covering all major template sections with relevant details about the feature, rationale, changes, and impact.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch rerender-on-mount

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Comment thread packages/react/src/editor/BlockNoteView.tsx
Copy link
Copy Markdown
Contributor

@nperez0111 nperez0111 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer a different solution here, this is too much. Especially for an issue we can't reliably reproduce

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Apr 16, 2026

Open in StackBlitz

@blocknote/ariakit

npm i https://pkg.pr.new/@blocknote/ariakit@2619

@blocknote/code-block

npm i https://pkg.pr.new/@blocknote/code-block@2619

@blocknote/core

npm i https://pkg.pr.new/@blocknote/core@2619

@blocknote/mantine

npm i https://pkg.pr.new/@blocknote/mantine@2619

@blocknote/react

npm i https://pkg.pr.new/@blocknote/react@2619

@blocknote/server-util

npm i https://pkg.pr.new/@blocknote/server-util@2619

@blocknote/shadcn

npm i https://pkg.pr.new/@blocknote/shadcn@2619

@blocknote/xl-ai

npm i https://pkg.pr.new/@blocknote/xl-ai@2619

@blocknote/xl-docx-exporter

npm i https://pkg.pr.new/@blocknote/xl-docx-exporter@2619

@blocknote/xl-email-exporter

npm i https://pkg.pr.new/@blocknote/xl-email-exporter@2619

@blocknote/xl-multi-column

npm i https://pkg.pr.new/@blocknote/xl-multi-column@2619

@blocknote/xl-odt-exporter

npm i https://pkg.pr.new/@blocknote/xl-odt-exporter@2619

@blocknote/xl-pdf-exporter

npm i https://pkg.pr.new/@blocknote/xl-pdf-exporter@2619

commit: c863f86

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/react/src/components/SuggestionMenu/hooks/useSuggestionMenuKeyboardNavigation.ts (1)

9-9: Consider removing _editor from the signature if external consumers don't depend on it.

Since the hook is exported publicly and the parameter is unused, removal would be a breaking change. If this hook isn't consumed by external packages, dropping the argument would simplify the API and avoid confusion for maintainers.

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@packages/react/src/components/SuggestionMenu/GridSuggestionMenu/hooks/useGridSuggestionMenuKeyboardNavigation.ts`:
- Around line 8-15: The hook useGridSuggestionMenuKeyboardNavigation currently
accepts an explicit _editor argument but ignores it by always using
useEditorDOMElement() from context; update the implementation to pass the
explicit editor through to useEditorDOMElement (i.e., call
useEditorDOMElement(editor) or similar) and modify useEditorDOMElement to accept
an optional editorOverride parameter that falls back to useBlockNoteEditor when
not provided so keyboard listeners target the provided editor instance and
restore correct behavior for multi-editor/custom usage.

In `@packages/react/src/hooks/useEditorDomElement.ts`:
- Around line 24-33: The handler passed to editor._tiptapEditor.on("create")
uses setInitialized(true) which is idempotent and prevents re-renders after the
first create; change the effect to update state with a non-idempotent update
(e.g. flip a boolean or increment a counter) so each "create" event forces a
render. Update the useEffect in useEditorDomElement.ts around setInitialized and
the editor._tiptapEditor.on/off("create") handlers to call setInitialized(prev
=> !prev) or setInitialized(n => n + 1) instead of setInitialized(true) so
descendant consumers see a fresh editor.domElement on every create.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c1dc22a0-e78c-431f-8f52-e29b4f4e4561

📥 Commits

Reviewing files that changed from the base of the PR and between 58457e2 and c20d1c1.

📒 Files selected for processing (10)
  • packages/core/src/editor/BlockNoteEditor.ts
  • packages/react/src/components/FormattingToolbar/DefaultButtons/CreateLinkButton.tsx
  • packages/react/src/components/LinkToolbar/LinkToolbarController.tsx
  • packages/react/src/components/Popovers/PositionPopover.tsx
  • packages/react/src/components/SuggestionMenu/GridSuggestionMenu/GridSuggestionMenuController.tsx
  • packages/react/src/components/SuggestionMenu/GridSuggestionMenu/hooks/useGridSuggestionMenuKeyboardNavigation.ts
  • packages/react/src/components/SuggestionMenu/SuggestionMenuController.tsx
  • packages/react/src/components/SuggestionMenu/hooks/useSuggestionMenuKeyboardNavigation.ts
  • packages/react/src/hooks/useEditorDomElement.ts
  • packages/react/src/index.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/core/src/editor/BlockNoteEditor.ts

Comment thread packages/react/src/hooks/useEditorDomElement.ts Outdated
@matthewlipski matthewlipski changed the title fix: Editor re-render on mount fix: useEditorDOMElement hook Apr 17, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/react/src/hooks/useEditorState.ts (1)

138-142: Minor: hoist EVENT_TYPES out of watch.

It's a constant and currently re-allocated on every watch() invocation; lifting it to module scope also lets you derive the on union from keyof typeof EVENT_TYPES to keep the type and runtime map in sync.

♻️ Proposed refactor
+const EVENT_TYPES = {
+  all: ["transaction", "create", "mount", "unmount"],
+  mount: ["create", "mount", "unmount"],
+  selection: ["selectionUpdate"],
+  change: ["update"],
+} as const;
+
@@
-      const EVENT_TYPES = {
-        all: ["transaction", "create", "mount", "unmount"],
-        mount: ["create", "mount", "unmount"],
-        selection: ["selectionUpdate"],
-        change: ["update"],
-      } as const;
-
       for (const eventType of EVENT_TYPES[on]) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/react/src/hooks/useEditorState.ts` around lines 138 - 142, Hoist the
EVENT_TYPES constant out of the watch() function to module scope so it isn't
re-created on every invocation; move the object currently defined inside watch
to a top-level const named EVENT_TYPES and update watch to reference it. Also
derive the on union/type from keyof typeof EVENT_TYPES (instead of a separate
hard-coded union) so the TypeScript type stays in sync with the runtime map;
update any usages of the previous on type or inline literals to use the new
derived type. Ensure imports/exports and any references to EVENT_TYPES remain
consistent with the new module-scope symbol.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/react/src/hooks/useEditorState.ts`:
- Around line 138-142: Hoist the EVENT_TYPES constant out of the watch()
function to module scope so it isn't re-created on every invocation; move the
object currently defined inside watch to a top-level const named EVENT_TYPES and
update watch to reference it. Also derive the on union/type from keyof typeof
EVENT_TYPES (instead of a separate hard-coded union) so the TypeScript type
stays in sync with the runtime map; update any usages of the previous on type or
inline literals to use the new derived type. Ensure imports/exports and any
references to EVENT_TYPES remain consistent with the new module-scope symbol.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5835d25a-128a-4b75-ab97-2701623fa64f

📥 Commits

Reviewing files that changed from the base of the PR and between 88c14d4 and a9b8770.

📒 Files selected for processing (3)
  • packages/core/src/editor/BlockNoteEditor.ts
  • packages/react/src/hooks/useEditorDomElement.ts
  • packages/react/src/hooks/useEditorState.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/react/src/hooks/useEditorDomElement.ts
  • packages/core/src/editor/BlockNoteEditor.ts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Link: no menu appears on hover

2 participants